/*
*    Phantom v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include "SSI_ScriptClient.h"

CScriptClient::CScriptClient(socket_stream *an_input, socket_stream *an_output)  : CScript((ssi_stream*)an_input, (ssi_stream*)an_output)
, bPrompt(false)
{
	LineCtr = 0;
	bPrompt = true;
	
	RunMode = NORMAL_MODE;         // Default to NORMAL_MODE

	Debugger = (CDebugger*)&ClientDebugger;
	Debugger->input = an_input;
	Debugger->output = an_output;
}

CScriptClient::~CScriptClient()
{
}

/*
SSI_INT64 CScriptClient::Loop()
{
  SetInputName(String("SSI_Client"));

  while(WaitPrompt())
  {
  }

	//((socket_stream*)output)->CloseConnection();

  zap(Interp);
  zap(Engine);
  return 0;
}
*/

/*
SSI_BOOL CConsole::WaitPrompt()
{
	ScriptNode* aNode = 0;
  CScriptLine aLine;
	
	aNode = GetLine(aLine);
	if(!aNode) return 0;

  if(aLine.Line == "stop")
  {
    return 0;
  }
  Interp->Interpret(aLine.Line, aNode);
  return 1;
}
*/

/*
SSI_BOOL CConsole::GetNextLine(CScriptLine& Ret)
{
	ScriptNode* aNode = 0;
  aNode = GetLine(Ret);
	if(!aNode){
		return 0;
	}
  return 1;
}
*/

ScriptNode* CScriptClient::GetLine(CScriptLine& RetLine)
{
  if(CurrLine){
    CurrLine = CurrLine->next();
  }

	if(CurrLine){
    CurrLine->Get(RetLine);
		return CurrLine;
  }

	// This would occur only on the first line of a script
	//if(RunType == SSI_CLIENT_SCRIPT){
	//	RunType = 0;
		CurrLine = GetTopLine();
		if(CurrLine){
      CurrLine->Get(RetLine);
			return CurrLine;
		}
	//}else{  // RunType = SSI_CLIENT_CONSOLE
	//	SSI_CHAR Temp[MAX_SSI_CHARS];
	//  LineCtr++;
	//	String s;

		//if(RunMode == DEBUG_MODE){
		//	s.Format("DEBUG %d >>", LineCtr);
		//}else{
	//		s.Format("%d >>", LineCtr);
		//}
	//	if(bPrompt){
	//		output->Disp(s.Str());
	//	}

	//	if(!input->getline(Temp, MAX_SSI_CHARS)){
	//		return 0;
	//	}

	//	RetLine.Line = Temp;
	//	RetLine.LineNum = LineCtr;

	//	CurrLine = PushLine(RetLine);//Lines.push(RetLine);
	//	return CurrLine;
	//}
	return 0;
}



SSI_INT64 CScriptClient::ProcessDebug(){
	return 1;
}


SSI_INT64 CScriptClient::Query(const CScriptStatement& aStatement)
{
	if(Debugger && (RunMode == DEBUG_MODE)){
		if(!Debugger->Process(Engine, Interp, (CInput*)this, aStatement)){
			return 0;
		}
	}
	return CScript::Query(aStatement);
}


/*
SSI_BOOL CScriptClient::Load(const String& FileName)
{
  File aFile(FileName);
  if(!aFile.Status) return 0;

  SetInputName(FileName);
	SetFileName(FileName);

  String Line;
  SSI_INT64 ctr = 0;
  CScriptLine sLine;
	ClearLines();
  while(aFile.ReadLine(Line) >= 0)
  {
    ctr++;
    sLine.Line = Line;
    sLine.LineNum = ctr;
    PushLine(sLine);
  }

  FilePath = FileName;

  return 1;
}
*/

/*
SSI_BOOL CConsole::WaitPrompt()
{
	ScriptNode* aNode = 0;
  CScriptLine aLine;
	
	aNode = GetLine(aLine);
	if(!aNode) return 0;

  if(aLine.Line == "stop")
  {
    return 0;
  }
  Interp->Interpret(aLine.Line, aNode);
  return 1;
}
*/


SSI_INT64 CScriptClient::CClientDebugger::Process(CEngine* Engine, CInterpreter* Interp, CInput* Input,  const CScriptStatement& aStatement){

	static int doVERBOSE = 0;

	if(!Engine){
		return 0;
	}

	if(!Interp){
		return 0;
	}

	if(!Input){
		return 0;
	}

	if(!output){
		return 0;
	}

	if(!input){
		return 0;
	}

	// Iterators for input list and stop state
	vector<CInput*>::iterator itor;
	vector<int>::iterator stor;

	// This is the first input
	if(InputList->size() <= 0){
		InputList->push_back(Input);
		StopStates->push_back(1);
		debug_LastLine = 0;
	}


	int newInput = 0;

	// If Input not found in list, add it
	itor = find(InputList->begin(), InputList->end(), Input);
	if(itor == InputList->end()){
		InputList->push_back(Input);
		StopStates->push_back(0);
		itor = find(InputList->begin(), InputList->end(), Input);

		// LastLine no longer valid, force stop state check
		debug_LastLine = 0;
		newInput = 1;
	}

	itor++;
	int sz = InputList->size();

	// If not the last input, clear all remaining ones
	if(itor != InputList->end()){
		InputList->erase(itor, InputList->end());
		sz = sz - InputList->size();

		// Back up in stop state list
		int ctr = 1;
		stor = StopStates->end();
		--stor;
		while(ctr < sz){
			ctr++;
			--stor;
		}

		StopStates->erase(stor, StopStates->end());

		sz = StopStates->size();  // Just for checking... remove later
		if(sz != InputList->size()){
			sz = InputList->size();
			//printf("ERROR*** Sizes dont match\n");
      Engine->GetOutStream()->ErrorDisp("INTERNAL ERROR IN CCLIENTSCRIPT::CSCRIPTDEBUGGER::PROCESS", 1);
		}

		// If breaking out of Input, LastLine not valid and force JumpState check
		debug_LastLine = 0;
		newInput = 1;
	}		

	// Get last in input list and stop state
	itor = InputList->end();  // End is some ghost position
	--itor;
	stor = StopStates->end();
	--stor;

	// If it was a new input, and the JumpState is set, step into
	if(JumpState && newInput){
		*stor = 1;
		JumpState = 0;
	}

	// Dont show same line twice
	if(debug_LastLine == aStatement.LineID){
		return 1;
	}
	debug_LastLine = aStatement.LineID;

	// Get the current line
	ScriptNode* aNode = Input->GetByID(aStatement.LineID);
	if(!aNode){
		return 0;
	}
	CScriptLine aLine;
	aNode->Get(aLine);

	// Print line if verbose mode is on
	String Line;
	doVERBOSE = 1;
	if(doVERBOSE){
		Line.Format("%d: %s", aLine.LineNum, aLine.Line.Str());
		output->Disp(Line.Str(), 1);
	}

	// Breakpoint check
	CBreakpoint aBP;
	aBP.LineNumber = aLine.LineNum;
	aBP.FileName = Input->GetFileName();
	if(CheckBreakpoint(aBP) > 0){
		Line.Format("  {{ BP Hit - Line %d (%s) }}  ", aLine.LineNum, aBP.FileName.Str()); 
		output->Disp(Line.Str(), 1);
		SetGoCounter(0);
		SetToLine(0);

		// Set stops for all previous inputs
		vector<int>::iterator stor_temp;
		stor_temp = stor;
		while(*stor_temp != 1){
			*stor_temp = 1;
			if(stor_temp == StopStates->begin()){
				break;
			}
			--stor_temp;
		}
	}

	// If not a stop state, return
	if(*stor == 0){
		return 1;
	}

	// Check Go Counter
	if(debug_GoCtr > 0){
		SetGoCounter(debug_GoCtr - 1);
		return 1;
	}else if(debug_GoCtr < 0){
		return 1;  // OK, so this will go until end of script or breakpoint
	}

	// Check To Line
	if(debug_ToLine > 0){
		if(debug_ToLine <= aLine.LineNum){
			SetToLine(0);
		}else{
			return 1;
		}
	}

	// Debug command processing
	String Tok;
	String Delim = " ";
	String Rem;
	SSI_INT64 Pos;
	String sMess;

	// Handle User Input
	int bLoop = 1;
	while(bLoop){
		if(GetDebugInput(Input->GetFileName(), aLine, Line)){
		}else{
			//printf("Debug input request failed...\n");
			return 0;			
		}
		Line.Trim();
		
		if(Line == ""){
			// Allow <ENTER> to step through
			Line = "g 1";
		}
		if((Pos = Line.Parse(Delim, Rem, &Tok)) >= 0){
			if(Rem == "v"){
				DisplayVariable(Line, Engine);
			}else if(Rem == "e"){

				((socket_stream*)output)->m_Cmd.Set(RSP_ACK, 0, 0);
				if(!((socket_stream*)output)->sendDebug()){
					return 0;
				}
				// OK, so 'e' doesnt work because it doesnt add a statement to the statement list
				Interp->Interpret(Line, SSI_NULL);  // In debug mode, so dont send a curr node
				if(Interp->GetReturnState() || Interp->GetStopState()){
					return 0;
				}
			}else if((Rem == "g") || (Rem == "t")){
				int linenumber;
				if(sscanf(Line.Str(), "%d", &linenumber) != 1){
					sMess.Format("'g' and 't' format is 'g[ number]'");
					DisplayDebugError(sMess);
				}else{
					if(Rem == "t"){
						SetToLine(linenumber);
					}else{
						SetGoCounter(linenumber);
						if(debug_GoCtr > 0){
							SetGoCounter(debug_GoCtr - 1);
						}
					}
					bLoop = 0;
				}
				((socket_stream*)output)->m_Cmd.Set(RSP_ACK, 0, 0);
				if(!((socket_stream*)output)->sendDebug()){
					return 0;
				}

			}else if(Rem == "+b"){
				int LineNum;
				char buff[1024];
				if(sscanf(Line.Str(), "%d %[^\0]s", &LineNum, buff) != 2){
				}else{
					CBreakpoint aBP;
					aBP.LineNumber = LineNum;
					aBP.FileName = buff;
					AddBreakpoint(aBP);
				}
				((socket_stream*)output)->m_Cmd.Set(RSP_ACK, 0, 0);
				if(!((socket_stream*)output)->sendDebug()){
					return 0;
				}

			}else if(Rem == "-b"){
				int LineNum;
				char buff[1024];
				if(sscanf(Line.Str(), "%d %[^\0]s", &LineNum, buff) != 2){
				}else{
					CBreakpoint aBP;
					aBP.LineNumber = LineNum;
					aBP.FileName = buff;
					RemoveBreakpoint(aBP);
				}
				((socket_stream*)output)->m_Cmd.Set(RSP_ACK, 0, 0);
				if(!((socket_stream*)output)->sendDebug()){
					return 0;
				}

			}else{
				bLoop = 0;
			}
		}else{
			// Jump in function
			if(Line == "j"){
				JumpState = 1;
				SetToLine(0);
				SetGoCounter(0);
				bLoop = 0;

				((socket_stream*)output)->m_Cmd.Set(RSP_ACK, 0, 0);
				if(!((socket_stream*)output)->sendDebug()){
					return 0;
				}
			// Jump out of function
			}else if(Line == "o"){
				*stor = 0;
				bLoop = 0;

				((socket_stream*)output)->m_Cmd.Set(RSP_ACK, 0, 0);
				if(!((socket_stream*)output)->sendDebug()){
					return 0;
				}
			}else if(Line == "stop"){
				return 0;
			}else{
				bLoop = 0;
			}
		}
	}
	return 1;
}

CScriptClient::CClientDebugger::CClientDebugger() : CScript::CScriptDebugger()
{
}

CScriptClient::CClientDebugger::~CClientDebugger(){
}

SSI_INT64 CScriptClient::CClientDebugger::GetDebugInput(String& FileName, CScriptLine& CurrLine, String& Input)
{
	SSI_CHAR Buff[4096];
	SSI_INT64 cnt = input->getDebug(CurrLine.LineNum, FileName.Str(), Buff, 4096);
	if(cnt >= 4096){
		String sErr = "Maximum input is 4095 characters";
		DisplayDebugError(sErr);
		return 0;
	}
	if(cnt < 0){
		return 0;
	}
	Buff[4095] = 0;
	Buff[cnt] = 0;
	Input = Buff;
	return 1;
}

SSI_INT64 CScriptClient::CClientDebugger::DisplayVariable(String& VarName, CEngine* InEngine)
{
	String sMess;
	CVariable aVar;
	aVar.Name = VarName;
	aVar.SetType(dUNKNOWN);
	
	if(!InEngine->GetVariable(aVar)){
		sMess.Format("Variable '%s' not found", aVar.Name.Str());
		//output->Disp(sMess, 1);
		((socket_stream*)output)->m_Cmd.Set(SSI_VAR_RSP, sMess.GetLength(), sMess.Str());
		if(!((socket_stream*)output)->sendDebug()){
			return 0;
		}

	}else{
		sMess.Format("%s", aVar.GetWorkingData().GetString().Str());
		((socket_stream*)output)->m_Cmd.Set(SSI_VAR_RSP, sMess.GetLength(), sMess.Str());
		if(!((socket_stream*)output)->sendDebug()){
			return 0;
		}
	}
	return 1;
}


